﻿using System;
using System.Collections.Generic;
using XNOgre.Core;
using Microsoft.Xna.Framework;
using XNOgre.Graphics;

namespace XNOgre.Scene
{
    public class SceneNode : Node, IDisposable
    {
        protected List<MovableObject> movableObjects;
        protected BoundingBox worldAABB;

        internal SceneNode(SceneManager creator)
        {
            Creator = creator;
            IsInSceneGraph = false;

            movableObjects = new List<MovableObject>();
            NeedUpdate();
        }
        internal SceneNode(SceneManager creator, string name)
            : base(name)
        {
            Creator = creator;
            IsInSceneGraph = false;

            movableObjects = new List<MovableObject>();
            NeedUpdate();
        }
        public override void Dispose()
        {
            //deatch all objects manually to avoid needUpdate() call
            for (int i = 0; i < movableObjects.Count; i++)
            {
                movableObjects[i].NotifyAttached(null);
            }

            movableObjects.Clear();
        }

        internal bool IsInSceneGraph { get; set; }
        public SceneManager Creator { get; private set; }
        public SceneNode ParentSceneNode
        {
            get { return (SceneNode)Parent; }
        }
        public BoundingBox WorldBoundingBox
        {
            get
            {
                return worldAABB;
            }
        }
        public int NumAttachedObject
        {
            get { return movableObjects.Count; }
        }
        public Vector3 Direction
        {
            get
            {
                return Vector3.Transform(Vector3.Forward, Orientation);
            }
            set
            {
                if (value == Vector3.Zero)
                    return;

                Vector3 zAdjuctVec = -value;
                zAdjuctVec.Normalize();

                Quaternion targetWorldOrientation;


                Vector3[] axes = new Vector3[3];
                Orientation.ToAxes(out axes);
                Quaternion rotQuat;

                if ((axes[2] + zAdjuctVec).LengthSquared() < 0.00005f)
                {
                    // Oops, a 180 degree turn (infinite possible rotation axes)
                    // Default to yaw i.e. use current UP
                    rotQuat = axes[2].RotationTo(zAdjuctVec);
                }
                else
                {
                    rotQuat = axes[2].RotationTo(zAdjuctVec);
                }
                targetWorldOrientation = rotQuat * Orientation;


                Orientation = targetWorldOrientation;


                NeedUpdate();
            }
        }

        protected override void UpdateFromParentImpl()
        {
            base.UpdateFromParentImpl();

            for (int i = 0; i < movableObjects.Count; i++)
            {
                movableObjects[i].NotifyMoved();
            }
        }
        public override void Rotate(Quaternion rotation, TransformSpace relativeTo = TransformSpace.Local)
        {
            base.Rotate(rotation, relativeTo);
            //update bounding box
            for (int i = 0; i < this.movableObjects.Count; i++)
            {
                movableObjects[i].UpdateBoundingBox();
            }
        }
        public override void Update(bool updateChildren, bool parentHasChanged)
        {
            base.Update(updateChildren, parentHasChanged);
            UpdateBounds();
        }
        public void UpdateBounds()
        {
            worldAABB = new BoundingBox();

            for (int i = 0; i < movableObjects.Count; i++)
            {
                worldAABB = worldAABB.Merge(movableObjects[i].BoundingBox);
            }

            for (int i = 0; i < children.Count; i++)
            {
                if (children[i] is SceneNode)
                {
                    SceneNode sceneChild = (children[i] as SceneNode);
                    worldAABB = worldAABB.Merge(sceneChild.worldAABB);
                }
            }

        }
        protected override Node CreateChildImpl()
        {
            return Creator.CreateSceneNode();
        }
        protected override Node CreateChildImpl(string name)
        {
            return Creator.CreateSceneNode(name);
        }
        protected override void SetParent(Node parent)
        {
            base.SetParent(parent);
        }

        public virtual void AttachObject(MovableObject obj)
        {
            if (obj.IsAttached)
            {
                throw new XNOgreException("Object already attached.");
            }

            obj.NotifyAttached(this);
            movableObjects.Add(obj);

            NeedUpdate();
        }
        public virtual MovableObject GetAttachedObject(int index)
        {
            return movableObjects[index];
        }
        public virtual MovableObject GetAttachedObject(string name)
        {
            for (int i = 0; i < NumAttachedObject; i++)
            {
                if (movableObjects[i].Name == name)
                    return movableObjects[i];
            }
            return null;
        }
        public virtual void DetachObject(MovableObject obj)
        {
            for (int i = 0; i < movableObjects.Count; i++)
            {
                MovableObject mobj = movableObjects[i];
                if (mobj.Equals(obj))
                {
                    movableObjects.RemoveAt(i);
                    mobj.NotifyAttached(null);
                    NeedUpdate();
                    break;
                }
            }
        }
        public virtual MovableObject DetachObject(string name)
        {
            for (int i = 0; i < movableObjects.Count; i++)
            {
                if (movableObjects[i].Name == name)
                {
                    MovableObject ret = movableObjects[i];
                    movableObjects.RemoveAt(i);
                    ret.NotifyAttached(null);
                    NeedUpdate();

                    return ret;
                }
            }

            throw new XNOgreException("Object with name " + name + " not attached.");
        }
        public virtual MovableObject DetachObject(int index)
        {
            MovableObject ret = movableObjects[index];
            movableObjects.RemoveAt(index);
            ret.NotifyAttached(null);
            NeedUpdate();

            return ret;
        }
        public virtual void DetachAllObjects()
        {
            for (int i = 0; i < movableObjects.Count; i++)
            {
                movableObjects[i].NotifyAttached(null);
            }

            movableObjects.Clear();
            NeedUpdate();
        }

        public void NotifyRootNode()
        {
            IsInSceneGraph = true;
        }

        public virtual IEnumerable<IRenderable> FindVisibleObjects(Camera cam, bool includeChildren = true)
        {
            List<IRenderable> visibleObjects = new List<IRenderable>();
            foreach (var obj in movableObjects)
            {
                if (obj is Light)
                    continue;

                if (obj.Visible)
                {
                    obj.NotifyCurrentCamera(cam);
                    obj.UpdateRenderQueue(ref visibleObjects);
                }
            }

            if (includeChildren)
            {
                for (int i = 0; i < children.Count; i++)
                {
                    if (children[i] is SceneNode)
                    {
                        SceneNode childNode = (children[i] as SceneNode);
                        visibleObjects.AddRange(childNode.FindVisibleObjects(cam, includeChildren));
                    }
                }
            }

            return visibleObjects;
        }

        public virtual void RemoveAndDestroyChild(string name)
        {
            var child = (SceneNode)GetChild(name);
            child.RemoveAndDestroyAllChildren();

            RemoveChild(name);
            child.Creator.DestroySceneNode(name);
        }
        public virtual void RemoveAndDestroyChild(int index)
        {
            var child = (SceneNode)GetChild(index);
            child.RemoveAndDestroyAllChildren();

            RemoveChild(index);
            child.Creator.DestroySceneNode(child.name);
        }
        public virtual void RemoveAndDestroyAllChildren()
        {
            for (int i = 0; i < children.Count; i++)
            {
                SceneNode sn = (SceneNode)children[i];
                sn.RemoveAndDestroyAllChildren();
                sn.Creator.DestroySceneNode(sn.name);
            }

            children.Clear();
            NeedUpdate();
        }

        public virtual SceneNode CreateChildSceneNode()
        {
            return CreateChildSceneNode(Root.GenerateName(this.GetType()));
        }
        public virtual SceneNode CreateChildSceneNode(string name)
        {
            return CreateChildSceneNode(name, Vector3.Zero, Quaternion.Identity);
        }
        public virtual SceneNode CreateChildSceneNode(Vector3 translate)
        {
            return CreateChildSceneNode(Root.GenerateName(this.GetType()), translate);
        }
        public virtual SceneNode CreateChildSceneNode(string name, Vector3 translate)
        {
            return CreateChildSceneNode(name, translate, Quaternion.Identity);
        }
        public virtual SceneNode CreateChildSceneNode(string name, Vector3 translate, Quaternion rotate)
        {
            return (SceneNode)this.CreateChild(name, translate, rotate);
        }
        public void LookAt(Vector3 target, TransformSpace relativeTo)
        {
            Vector3 origin = new Vector3();
            switch (relativeTo)
            {
                case TransformSpace.Local:
                    origin = Vector3.Zero;
                    break;
                case TransformSpace.Parent:
                    origin = position;
                    break;
                case TransformSpace.World:
                    origin = DerivedPosition;
                    break;
            }

            this.Direction = target - origin;
        }
        public void SetVisible(bool visible, bool cascade = true)
        {
            for (int i = 0; i < movableObjects.Count; i++)
            {
                movableObjects[i].Visible = visible;
            }

            if (cascade)
            {
                for (int i = 0; i < children.Count; i++)
                {
                    (children[i] as SceneNode).SetVisible(visible, cascade);
                }
            }
        }
        public void FlipVisibility(bool cascade = true)
        {
            for (int i = 0; i < movableObjects.Count; i++)
            {
                movableObjects[i].Visible = !movableObjects[i].Visible;
            }

            if (cascade)
            {
                for (int i = 0; i < children.Count; i++)
                {
                    (children[i] as SceneNode).FlipVisibility(cascade);
                }
            }
        }
    }
}
